#pragma once

#include <iostream>

namespace VariadicParamTemplate
{
    /**
     * 1、可以接受任意数量的模板参数的模板，称为可变参数模板
     * if(sizeof...(args)>0)，就算 sizeof...(args)==0，也会实例化该if下调用的函数
     */

    // 当传入print函数的参数为空时，就会调用这个函数
    // void print(){}

    template <typename T>
    void print(T arg)
    {
        std::cout << arg << std::endl;
    }

    template <typename T, typename... Types>
    void print(T firstArg, Types... args)
    {
        // std::cout << firstArg<< std::endl;
        print(firstArg);
        print(args...);
    }

    void print2() {}

    // 可以使用sizeof操作符
    template <typename T, typename... Types>
    void print2(T arg, Types... args)
    {
        int typeSize = sizeof...(Types);
        int argsSize = sizeof...(args);
        std::cout << typeSize << " " << argsSize << std::endl;
        std::cout << arg << std::endl;
        if (argsSize > 0) // 就算返回false，下面的模板也要实例化。当argsSize==0的时候，就会调用print2()，如果不提供print2()，就会报错
        {
            print2(args...);
        }
    }

    /**
     * 2、折叠表达式：重要！！！
     * 2.1.可能的折叠表达式展开后的情况：
     *  (... op pack) -> (((pack1 op pack2) op pack3) ... op packN)
     *  (pack op ...) -> (pack1 op (... (packN-1 op packN)))
     *  (init op ... op pack) -> (((init op pack1) op pack2) ... op packN)
     *  (pack op ... op init) -> (pack1 op (... (packN op init)))
     */

    struct _addOne
    {
        // 使用仿函数。注意这里是函数模板，而不是类模板。
        template <typename T>
        T &operator()(T &val)
        {
            val += 1;
            return val;
        };
    };

    template <typename... T>
    auto foldSum(T... args)
    {
        // _addOne<T> addOne; // 如果仿函数的实现使用类模板，则会导致传入仿函数的T和传入foldSum的T...是两个不同的东西
        _addOne addOne; // 使用函数模板实现
        // return (... + args); // foldSum(1,2,3,4,5); 会展开为 ((((1+2)+3)+4)+5)
        // return (...,(args+args)); // foldSum(1,2,3); 会展开为 ((1+1),(2+2)),(3+3)，但是返回的是最后一个,后面的值，也就是 6
        // return (... + addOne(args)); // foldSum(1,2,3,4,5); 会展开为 ((((addOne(1)+addOne(2))+addOne(3))+addOne(4))+addOne(5))
        return 0;
    }

    template <typename... Types>
    void printDouble_(Types... args)
    {
        // 注意以下两种方式的语法格式
        (..., (std::cout << (args + args) << std::endl)); // 直接使用折叠模板
        print((args + args)...);                          // 传入函数中使用折叠模板
    }

    /*
    // 调用：foldSum(1,2,3); 会生成以下代码（来自 cppinsights.io）：

    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    int foldSum<int, int, int>(int __args0, int __args1, int __args2)
    {
        _addOne addOne;
        return (addOne.operator()(__args0) + addOne.operator()(__args1)) + addOne.operator()(__args2);
    }
    #endif

    */

    /**
     * 3、应用
     * 典型的应用是转发可变数量的类型参数，例如：
     * namespace std{
     *   template<typename T, typename... Args> shared_ptr<T>
     *       make_shared(Args&&... args);
     *
     *   template<typename T, typename Allocator = allocator<T>>
     *   class vector
     *   {
     *   public:
     *       template<typename... Args>
     *       reference emplace_back(Args&&... args);
     *   ...
     *   };
     * }
     */

    /**
     * 4、类模板和表达式
     * 4.1.表达式
     * 4.2.索引
     * 4.3.类模板
     * 4.4.推导策略
     * 4.5.基类和using
     */

    // 4.1.表达式：表达式需要使用()包含，否则编译器不知道表达式的边界
    template <typename... Types>
    void printDouble(Types... args)
    {
        // 注意以下两种方式的语法格式
        (..., (std::cout << (args + args) << std::endl)); // 直接打印
        print((args + args)...);                          // 传入函数中打印
    }
    /* printDouble(1,2.5,3); 生成：
    #ifdef INSIGHTS_USE_TEMPLATE
     template<>
     void printDouble<int, double, int>(int __args0, double __args1, int __args2)
     {
         ((std::cout.operator<<((__args0 + __args0)).operator<<(std::endl)) , (std::cout.operator<<((__args1 + __args1)).operator<<(std::endl))) , (std::cout.operator<<((__args2 + __args2)).operator<<(std::endl));
     }
     #endif
    */
    // 判断传入的类型是否都相同
    template <typename T1, typename... TN>
    constexpr bool isHomogeneous(T1, TN...)
    {
        return (... && (std::is_same_v<T1, TN>));
    }

    // 4.2.索引
    // 对指定索引的值进行求和
    template <typename T, typename... Idx>
    typename T::value_type getSum(const T &container, Idx... idxs)
    {
        return (... + container[idxs]);
    }

    // 打印指定索引的值
    template <typename T, typename... Idx>
    void printByIdx(const T &container, Idx... idxs)
    {
        (..., (std::cout << container[idxs] << std::endl)); // 直接打印
        print(container[idxs]...);                          // 传入函数中打印
    }

    // 4.3.类模板 后面才讨论
    /*
    例如：
    template<typename... Elements>
    class Tuple;
     */

    // 4.4.推导策略：类似2.9，显示指定传入的参数推导出哪种类型
    /*
    例如：
    #if __cpp_deduction_guides >= 201606
    template<typename _Tp, typename... _Up>
    array(_Tp, _Up...)
    -> array<enable_if_t<(is_same_v<_Tp, _Up> && ...), _Tp>,
        1 + sizeof...(_Up)>;
    #endif
    */
   
    template <typename T, std::size_t U>
    class Test
    {
    public:
    };

    template <typename T, typename... U>
    Test(T, U...) 
    -> Test<std::enable_if_t<(std::is_same_v<T, U> && ...), T>, // 确保传入的T、U所有的类型一致
        1 + sizeof...(U)>; // 数量

    /**
     * 4.5.基类和using 见后面，如26.4
     */
};
